mod cobcoord;
mod cobdirection;
mod cobregister;
mod cobtranslator;
mod cobunit;

pub use cobcoord::COBCoord;
pub use cobdirection::COBDirection;
pub use cobunit::*;
use cobregister::{COBSelRegister, COBSwRegister, SignalDirection};
use cobtranslator::COBTranslator;

pub const COBUNIT_SIZE: usize = 16;
pub const COB_INDEX_SIZE: usize = COBUNIT_SIZE * COBUNIT_WILTON_SIZE;

#[allow(unused)]
pub struct COBConnector {
    pub from_dir: COBDirection,
    pub to_dir: COBDirection,
    pub from_index: usize,
    pub to_index: usize,

    pub sw_reg: COBSwRegister,

    t_to_c_sel_reg: COBSelRegister,
    c_to_t_sel_reg: COBSelRegister,
}

// MARK: Better connector, maybe type state

impl COBConnector {
    pub fn is_connected(&self) -> bool {
        self.sw_reg.get()
    }

    pub fn connect(&self) {
        self.sw_reg.set(true);
        self.t_to_c_sel_reg.set_track_to_cob();
        self.c_to_t_sel_reg.set_cob_to_track();
    }

    pub fn disconnect(&self) {
        self.sw_reg.set(false);
        self.t_to_c_sel_reg.set_floating();
        self.c_to_t_sel_reg.set_floating();
    }
}

#[derive(Clone)]
pub struct COB {
    cobunits: Vec<COBUnit>,
}

impl COB {
    pub fn new() -> Self {
        Self {
            cobunits: (0..COBUNIT_SIZE)
                .into_iter()
                .map(|_| COBUnit::new())
                .collect(),
        }
    }
}

impl COB {
    pub fn adjacent_connectors_from_left(&self, from_track_index: usize) -> Vec<COBConnector> {
        self.adjacent_connectors(COBDirection::Left, from_track_index)
    }

    pub fn adjacent_connectors_from_right(&self, from_track_index: usize) -> Vec<COBConnector> {
        self.adjacent_connectors(COBDirection::Right, from_track_index)
    }

    pub fn adjacent_connectors_from_up(&self, from_track_index: usize) -> Vec<COBConnector> {
        self.adjacent_connectors(COBDirection::Up, from_track_index)
    }

    pub fn adjacent_connectors_from_down(&self, from_track_index: usize) -> Vec<COBConnector> {
        self.adjacent_connectors(COBDirection::Down, from_track_index)
    }

    fn adjacent_connectors(
        &self,
        from_dir: COBDirection,
        from_track_index: usize,
    ) -> Vec<COBConnector> {
        let from_cob_index = COBTranslator::track_index_to_cob_index(from_track_index);

        let (cobunits_index, from_cobunit_index) =
            COB::cob_index_to_cobunit_info(from_dir, from_cob_index);
        let cobunit = &self.cobunits[cobunits_index];

        cobunit
            .adjacent_connectors(from_dir, from_cobunit_index).into_iter()
            .map(|cobunit_connector| -> COBConnector {
                let to_cob_index = COB::cobunit_info_to_cob_index(
                    cobunit_connector.to_dir,
                    (cobunits_index, cobunit_connector.to_index),
                );
                let to_track_index = COBTranslator::cob_index_to_track_index(to_cob_index);

                COBConnector {
                    from_dir: COBDirection::Left,
                    to_dir: cobunit_connector.to_dir,
                    from_index: from_track_index,
                    to_index: to_track_index,
                    sw_reg: cobunit_connector.sw_reg,
                    t_to_c_sel_reg: cobunit_connector.t_to_c_sel_reg,
                    c_to_t_sel_reg: cobunit_connector.c_to_t_sel_reg,
                }
            })
            .collect()
    }
}

impl COB {
    pub fn sw_register(
        &self,
        from_dir: COBDirection,
        from_cob_index: usize,
        to_dir: COBDirection,
    ) -> COBSwRegister {
        let (cobunits_index, from_cobunit_index) =
            COB::cob_index_to_cobunit_info(from_dir, from_cob_index);
        self.cobunits[cobunits_index].sw_register(from_dir, from_cobunit_index, to_dir)
    }

    pub fn set_sw_register_value(
        &self,
        from_dir: COBDirection,
        from_cob_index: usize,
        to_dir: COBDirection,
        val: bool,
    ) {
        self.sw_register(from_dir, from_cob_index, to_dir).set(val);
    }

    pub fn get_sw_register_value(
        &self,
        from_dir: COBDirection,
        from_cob_index: usize,
        to_dir: COBDirection,
    ) -> bool {
        self.sw_register(from_dir, from_cob_index, to_dir).get()
    }

    pub fn get_sw_register_group_value(
        &self,
        from_dir: COBDirection,
        to_dir: COBDirection,
        group: usize,
    ) -> u32 {
        let mut value = 0;
        for bit in 0..32 {
            let from_index = (bit + group * 32) as usize;
            let bit_value = if self.get_sw_register_value(from_dir, from_index, to_dir) {
                1
            } else {
                0
            };
            value |= bit_value << bit;
        }
        value
    }
}

impl COB {
    pub fn sel_register(&self, dir: COBDirection, index: usize) -> COBSelRegister {
        let (cobunits_index, from_cobunit_index) = COB::cob_index_to_cobunit_info(dir, index);
        self.cobunits[cobunits_index].sel_register(dir, from_cobunit_index)
    }

    pub fn set_sel_register_value(
        &self,
        dir: COBDirection,
        index: usize,
        sig_dir: SignalDirection,
    ) {
        self.sel_register(dir, index).set(sig_dir);
    }

    pub fn get_sel_register_value(&self, dir: COBDirection, index: usize) -> SignalDirection {
        self.sel_register(dir, index).get()
    }

    pub fn get_sel_register_group_value(&self, dir: COBDirection, group: usize) -> u32 {
        let mut value = 0;
        for bit in 0..32 {
            let index = (bit + group * 32) as usize;
            let bit_value = if self.get_sel_register_value(dir, index).into() {
                1
            } else {
                0
            };
            value |= bit_value << bit;
        }
        value
    }
}

impl COB {
    fn cobunit_info_to_cob_index(_dir: COBDirection, cobunit_info: (usize, usize)) -> usize {
        let (cobunits_index, cobunit_index) = cobunit_info;
        assert!(cobunits_index < COBUNIT_SIZE);
        assert!(cobunit_index < COBUNIT_WILTON_SIZE);

        cobunits_index * COBUNIT_WILTON_SIZE + cobunit_index
    }

    fn cob_index_to_cobunit_info(_dir: COBDirection, cob_index: usize) -> (usize, usize) {
        assert!(cob_index < COB_INDEX_SIZE);

        (
            cob_index / COBUNIT_WILTON_SIZE,
            cob_index % COBUNIT_WILTON_SIZE,
        )
    }
}

impl COB {
    #[allow(unused)]
    pub fn track_index_map(from_dir: COBDirection, from_track_index: usize, to_dir: COBDirection) -> usize {
        let from_cob_index = COBTranslator::track_index_to_cob_index(from_track_index);
        let to_cob_index = COB::cob_index_map(from_dir, from_cob_index, to_dir);
        COBTranslator::cob_index_to_track_index(to_cob_index)
    }

    pub fn cob_index_map(from_dir: COBDirection, from_cob_index: usize, to_dir: COBDirection) -> usize {
        let (cobunits_index, from_cobunit_index) =
            COB::cob_index_to_cobunit_info(from_dir, from_cob_index);
        let to_cobunit_index = COBUnit::index_map(from_dir, from_cobunit_index, to_dir);
        COB::cobunit_info_to_cob_index(to_dir, (cobunits_index, to_cobunit_index))
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_cob_index_to_cobunit_info() {
        use COBDirection::*;

        assert_eq!(COB::cob_index_to_cobunit_info(Down, 104), (13, 0));
        assert_eq!(COB::cob_index_to_cobunit_info(Left, 0), (0, 0));
        assert_eq!(COB::cob_index_to_cobunit_info(Up, 0), (0, 0));
        assert_eq!(COB::cob_index_to_cobunit_info(Right, 78), (9, 6));
        assert_eq!(COB::cob_index_to_cobunit_info(Right, 29), (3, 5));
        assert_eq!(COB::cob_index_to_cobunit_info(Right, 30), (3, 6));
        assert_eq!(COB::cob_index_to_cobunit_info(Right, 31), (3, 7));
    }

    #[test]
    fn test_cobunit_info_to_cob_index() {
        use COBDirection::*;

        assert_eq!(COB::cobunit_info_to_cob_index(Left, (0, 0)), 0);
        assert_eq!(COB::cobunit_info_to_cob_index(Up, (0, 0)), 0);
        assert_eq!(COB::cobunit_info_to_cob_index(Right, (9, 6)), 78);
    }

    #[test]
    fn test_track_index_map() {
        use COBDirection::*;

        assert_eq!(COB::track_index_map(Down, 69, Right), 117);
        assert_eq!(COB::track_index_map(Left, 2, Up), 50);
        assert_eq!(COB::track_index_map(Down, 117, Right), 69);
        assert_eq!(COB::track_index_map(Left, 18, Up), 34);
        assert_eq!(COB::track_index_map(Down, 101, Right), 85);
        assert_eq!(COB::track_index_map(Left, 34, Up), 18);
        assert_eq!(COB::track_index_map(Up, 65, Right), 73);

        assert_eq!(COB::track_index_map(Left, 0, Down), 8);
        assert_eq!(COB::track_index_map(Left, 0, Up), 48);

        assert_eq!(COB::track_index_map(Right, 0, Down), 48);
    }

    #[test]
    fn test_cob_index_map() {
        use COBDirection::*;

        assert_eq!(COB::cob_index_map(Up, 72, Right), 73);
        assert_eq!(COB::cob_index_map(Up, 90, Right), 91);
        assert_eq!(COB::cob_index_map(Down, 72, Right), 78);
        assert_eq!(COB::cob_index_map(Down, 104, Right), 110);
    }

    #[test]
    fn test_adjacent_connectors() {
        use COBDirection::*;

        let cob = COB::new();

        let cs = cob.adjacent_connectors(Left, 0);
        assert_eq!(cs.len(), 3);

        let cs = cob.adjacent_connectors(Left, 0);
        assert_eq!(cs.len(), 3);
    }
}
